package com.nulldev.util.web.YouTubeExtractor.dfs_fix;

import static com.nulldev.util.web.YouTubeExtractor.util.YouTubeExtractorUtil.validateFunctionName;

import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.time.Duration;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.io.file.FileUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.HttpClient.HttpClient;
import com.nulldev.util.web.HttpClient.HttpResponse;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.URLUtils.fuckTracking.FuckTracking;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.YoutubeExtractionOptions;
import com.nulldev.util.web.YouTubeExtractor.signatures.ExtractedSignatureData;
import com.nulldev.util.web.YouTubeExtractor.signatures.SignatureExtractor;
import com.nulldev.util.web.YouTubeExtractor.signatures.SignatureUpdates;
import com.nulldev.util.web.YouTubeExtractor.signatures.extractor.Extractor;

public class DFSFix {

	/*
	 * DFS: Download Fucking Speed Fix Also known as Cipher 9.0 (lmao, I was right)
	 */

	private static final Logger logger = LoggerFactory.getLoggerD(Arguments.hasArgument("--debug", "-debug", "--trace", "--DFSFix.debug"), true);

	public static ExtractedSignatureData cipher_v9(final String jsFile) {
		String subStringToValue = StringsUtil.substringBetweenO(jsFile, "=function(a){var b=a.split(\"\"),c=[", "}return b.join(\"\")};");
		if (subStringToValue == null)
			subStringToValue = StringsUtil.substringBetweenO(jsFile, "=function(a){a=a.split(\"\");var b=[", ");return a.join(\"\")};");
		if (subStringToValue == null)
			return null;
		final int index = jsFile.indexOf(subStringToValue);
		final String r = validateFunctionName(
				jsFile.substring(index - "=function(a){var b=a.split(\"\"),c=[".length() - 3, index - "=function(a){var b=a.split(\"\"),c=[".length()));
		logger.debug("Function name: " + r);
		final String function = r + "=function(a){var b=a.split(\"\"),c=[" + subStringToValue + "}return b.join(\"\")" + "};";
		logger.debug("[cipher_v9] function: " + function);
		return new ExtractedSignatureData(function, function, r);
	}

	public static ExtractedSignatureData autoVideoID(final String videoID, final YoutubeExtractionOptions... options) throws Exception {
		logger.debug("[trace->autoVideoID] call");
		Entry[] he = new Entry[0];
		logger.debug("[trace->autoVideoID] he MADE");
		if (ArrayUtils.contains(options, YoutubeExtractionOptions.ENFORCE_PRIVACY_MODE)) {
			he = ArrayUtils.add(he, new Entry("Cookie", StringsUtil.randomString(64)));
			logger.debug("[trace->autoVideoID] he ADD COOKIE");
			he = ArrayUtils.addAll(he, FuckTracking.protect());
			logger.debug("[trace->autoVideoID] he FT ADD");
		} else if (ArrayUtils.contains(options, YoutubeExtractionOptions.EMULATE_INSECURE_PROXY)) {
			he = ArrayUtils.addAll(he, FuckTracking.protect());
			logger.debug("[trace->autoVideoID] he FT ADD");
		}
		final StringBuilder s = new StringBuilder();
		logger.debug("[trace->autoVideoID] StringBuilder::new");
		if (ArrayUtils.contains(options, YoutubeExtractionOptions.ADD_CONTROVERSY_BYPASS)) {
			s.append("&bpctr=" + System.currentTimeMillis());
			logger.debug("[trace->autoVideoID] S ADD ACB");
		}
		HttpResponse<String> _result;
		logger.debug("[trace->autoVideoID] _result declare");
		do {
			try {
				_result = HttpClient.getString().url(videoID == null ? "https://www.youtube.com/" : "https://www.youtube.com/watch?v=" + videoID + s.toString())
						.headers(he).timeout(Duration.ofSeconds(20)).create();
				logger.debug("[trace->autoVideoID] _result create");
				break;
			} catch (Exception e) {
				if (e instanceof InterruptedIOException) {
					// FIXME: No clue why this is thrown either way, just retry.
					try {
						Thread.sleep(50L);
					} catch (Exception e1) {
						Thread.interrupted();
					}
				} else {
					logger.error("autoVideoID(): Encountered an error, trying again.", e);
					try {
						final boolean isCE = e instanceof ConnectException;
						Thread.sleep(RandomUtil.randLong(isCE ? 500L : 50L, 1750L));
					} catch (Exception e1) {
						Thread.interrupted();
					}
				}
			}
		} while (true);
		final HttpResponse<String> result = _result;
		logger.debug("[trace->autoVideoID] result fetch");
		if (!result.successful())
			throw new RuntimeException("fromVideoID: Failed to get OK result! [got: " + result.status() + "]");
		logger.debug("[trace->autoVideoID] result SUCCESSFUL");
		final String r = result.dataThenClose();
		logger.debug("[trace->autoVideoID] result READ AND CLOSED");
		final String playerURL = SignatureExtractor.getPlayerURL(r, options);
		logger.debug("autoVideoID-deadlock-fix:playerURL: " + playerURL);
		logger.debug("autoVideoID-deadlock-fix: set cachedPlayerURL!");
		if (YouTubeExtractor.extractor.get() != null && SignatureUpdates.getCachedPlayerURL() != null
				&& SignatureUpdates.getCachedPlayerURL().equalsIgnoreCase(playerURL)) {
			logger.info("Deferred DFS update, already up to date.");
			return YouTubeExtractor.extractor.get().getExtractedData();
		} else if (SignatureUpdates.getCachedPlayerURL() == null) {
			logger.warn("PLAYER_URL missing, accquiring decipherer!");
			logger.debug("CURRENT_PLAYER_URL: " + SignatureUpdates.getCachedPlayerURL());
			logger.debug("PLAYER_URL: " + playerURL);
			SignatureUpdates.setCachedPlayerURL(playerURL);
		} else {
			logger.debug("Set CURRENT_PLAYER_URL to '" + playerURL + "'.");
			SignatureUpdates.setCachedPlayerURL(playerURL);
		}
		return fromData(r, playerURL, options);
	}

	public static ExtractedSignatureData fromData(final String page, String playerURL, final YoutubeExtractionOptions... options) throws Exception {
		if (playerURL == null || playerURL.isEmpty())
			playerURL = SignatureExtractor.getPlayerURL(page, options);
		Entry[] he = new Entry[0];
		if (ArrayUtils.contains(options, YoutubeExtractionOptions.ENFORCE_PRIVACY_MODE)) {
			he = ArrayUtils.add(he, new Entry("Cookie", StringsUtil.randomString(64)));
			he = ArrayUtils.addAll(he, FuckTracking.protect());
		} else if (ArrayUtils.contains(options, YoutubeExtractionOptions.EMULATE_INSECURE_PROXY)) {
			he = ArrayUtils.addAll(he, FuckTracking.protect());
		}
		final HttpResponse<String> result = HttpClient.getString().url(playerURL).headers(he).timeout(Duration.ofSeconds(300)) /* PROJECT_SLOW_CONN */
				.create();
		if (!result.successful())
			throw new RuntimeException("fromData: Failed to get OK result! [got: " + result.status() + "]");
		logger.info("[v9] player size: " + FileUtil.convertToStringRepresentation(result.length(true)));
		return cipher_v9(result.dataThenClose());
	}

	public static void main(String[] args) throws Exception {
		YouTubeExtractor.extractor.getAuto();
		final Extractor es = new CachedDFSFixer().accquire();
		JVM.println(es);
		// outputs: TlA2RRCYh6V9jXJrxr
		JVM.println(es.decrypt("TxrJXs9V6hYCRR2Alr1xj"));
	}
}
